//
//  NetHelper.m
//  Etion
//
//  Created by  user on 11-3-4.
//  Copyright 2011 GuangZhouXuanWu. All rights reserved.
//

#import "XMSSimpleDownloadHelper.h"

#import "RequestChannelPool.h"

//@interface NSString (HTTPExtensions)
//
//- (BOOL)isHTTPContentType:(NSString *)prefixStr;
//
//@end
//
//@implementation NSString (HTTPExtensions)
//
//- (BOOL)isHTTPContentType:(NSString *)prefixStr
//{
//    BOOL result;
//    NSRange foundRange;
//
//    result = NO;
//
//    foundRange = [self rangeOfString:prefixStr options:NSAnchoredSearch | NSCaseInsensitiveSearch];
//    if (foundRange.location != NSNotFound)
//    {
//        assert(foundRange.location == 0);            // because it's anchored
//        if (foundRange.length == self.length)
//        {
//            result = YES;
//        } else
//        {
//            unichar nextChar;
//
//            nextChar = [self characterAtIndex:foundRange.length];
//            result = nextChar <= 32 || nextChar >= 127 || (strchr("()<>@,;:\\<>/[]?={}", nextChar) != NULL);
//        }
//        /*
//         From RFC 2616:
//
//         token          = 1*<any CHAR except CTLs or separators>
//         separators     = "(" | ")" | "<" | ">" | "@"
//         | "," | ";" | ":" | "\" | <">
//         | "/" | "[" | "]" | "?" | "="
//         | "{" | "}" | SP | HT
//
//         media-type     = type "/" subtype *( ";" parameter )
//         type           = token
//         subtype        = token
//         */
//    }
//    return result;
//}
//
//@end

typedef NS_ENUM(NSUInteger,NetHelperStatus)
{
    ENetHelperReady,
    ENetHelperDownloading,
    ENetHelperFinished,
    ENetHelperError,
    ENetHelperCancel
} ;

@interface XMSSimpleDownloadHelper ()<NSURLConnectionDataDelegate>

@end

@implementation XMSSimpleDownloadHelper
{
    NSString *_urlString;
    NSURLConnection *_connection;
    NSString *_filePath;
    NSOutputStream *_fileStream;
    
    NetHelperStatus _netStatus;
    
    id _attachment;
    
    long long _fileTotalSize;
    long long _fileWrittenSize;
    
    XMSDispatchQueueWrapper *_delegateQueue;
}

@synthesize filePath = _filePath;
@synthesize urlString = _urlString;
@synthesize attachment = _attachment;

- (id)initWithUrl:(NSString *)urlStr path:(NSString *)filePathStr delegate:(id)delegate
{
    if (self = [super init])
    {
        _urlString = [urlStr retain_mm];
        _filePath = [filePathStr retain_mm];
        _delegate = delegate;
        _netStatus = ENetHelperReady;

        _delegateQueue = [[XMSDispatchQueueWrapper alloc]initWithName:""];

        self.dispatchQueueWrapper = [[[XMSDispatchQueueWrapper alloc]initWithName:""]autorelease_mm];
    }
    return self;
}

-(id<XMSSimpleDownloadHelperDelegate>)delegate
{
    return (id<XMSSimpleDownloadHelperDelegate>)_delegate;
}

- (void)setDelegate:(id<XMSSimpleDownloadHelperDelegate>)delegate
{
    [_delegateQueue dispatchThreadSync:^
    {
        _delegate = delegate;
    }];
}

#pragma mark -
#pragma mark Status management

- (void)receiveDidStart
{
    NSString *s = [self.filePath stringByAppendingPathExtension:@"tmp"];
    if (s.length <= 0)
    {
        [self dispatchQueueRunType:EDispatchQueueRunTypeAsync checkTerminate:NO asyncBlock:^
        {
            [self stopReceiveWithStatus:@"保存路径不存在"];
        }];
    }
    else
    {
        _fileStream = [[NSOutputStream outputStreamToFileAtPath:s append:NO] retain_mm];
        [_fileStream open];

        _netStatus = ENetHelperDownloading;

        [_delegateQueue dispatchThreadAsync:^
        {
            [self.delegate simpleDownloadHelperDidStartConnect:self];
        }];
    }
}

- (void)receiveDidStopWithStatus:(NSString *)statusString
{
    if (statusString == nil)
    {
        _netStatus = ENetHelperFinished;
        [_delegateQueue dispatchThreadAsync:^
        {
            [self.delegate simpleDownloadHelperFinishLoading:self];
        }];
    }
    else
    {
        if ([statusString isEqualToString:CANCELNETHELPERREQUEST] == YES)
        {
            _netStatus = ENetHelperCancel;
            [_delegateQueue dispatchThreadAsync:^
            {
                if ([self.delegate respondsToSelector:@selector(simpleDownloadHelperCancelRequest:)] == YES)
                    [self.delegate simpleDownloadHelperCancelRequest:self];
            }];
        }
        else
        {
            _netStatus = ENetHelperError;
            [_delegateQueue dispatchThreadAsync:^
            {
                [self.delegate simpleDownloadHelper:self error:[NSError errorWithDomain:statusString code:0 userInfo:nil]];
            }];
        }
    }
}

#pragma mark -
#pragma mark Networking

- (BOOL)startReceiveWaitUntilDone
{
    [_delegateQueue dispatchThreadAsync:^
    {
        [self.delegate simpleDownloadHelperWillStartConnect:self];
    }];
    
    BOOL bResult = [self.channelPool addRequestChannelPoolRunLoop:^(NSRunLoop *runloop)
    {
        NSString *URLStr = self.urlString;
        if([self.delegate respondsToSelector:@selector(simpleDownloadHelper:needTransferURL:)])
            URLStr = [self.delegate simpleDownloadHelper:self needTransferURL:URLStr];
        
        NSURL *URL = [NSURL URLWithString:[URLStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:20];
        [request setValue:[NSString stringWithFormat:@"bytes=%@-",@(0)] forHTTPHeaderField:@"Range"];
        
        _connection = [[NSURLConnection alloc]initWithRequest:request delegate:self startImmediately:NO];
        [_connection scheduleInRunLoop:runloop forMode:NSDefaultRunLoopMode];
        [_connection start];
    }];
    
    [self waitingFinish];
    
    return bResult;
}

- (void)closeFileStream
{
    [_fileStream close];
}

- (void)stopReceiveWithStatus:(NSString *)statusString
{
    [self receiveDidStopWithStatus:statusString];
    
    [_connection cancel];

    [self.channelPool removeRequestChannelPoolRunLoop:^(NSRunLoop *runloop)
    {
        [_connection unscheduleFromRunLoop:runloop forMode:NSDefaultRunLoopMode];
    }];
    
    [self closeFileStream];

    [self stopWaiting];
}

- (void)cancelRequest
{
    [self terminateRequest];
}

- (BOOL)terminateSelfRequest:(BOOL)bRejectrequest
{
    [self dispatchQueueRunType:EDispatchQueueRunTypeAsync checkTerminate:NO asyncBlock:^
    {
        [self stopReceiveWithStatus:CANCELNETHELPERREQUEST];
    }];
    return YES;
}

- (void)connection:(NSURLConnection *)theConnection didReceiveResponse:(NSURLResponse *)response
{
    [self dispatchQueueRunType:EDispatchQueueRunTypeAsync checkTerminate:YES asyncBlock:^
    {
        NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *) response;
        
        if ((httpResponse.statusCode / 100) != 2)
        {
            [self stopReceiveWithStatus:[NSString stringWithFormat:@"网络错误 %zd", (ssize_t) httpResponse.statusCode]];
        }
        else
        {
            _fileTotalSize = [httpResponse expectedContentLength];
            
            [_delegateQueue dispatchThreadAsync:^
            {
                if ([self.delegate respondsToSelector:@selector(simpleDownloadHelper:fileTotalSize:)] == YES)
                    [self.delegate simpleDownloadHelper:self fileTotalSize:_fileTotalSize];
            }];
            
            [self receiveDidStart];
        }
    }];
}

- (void)connection:(NSURLConnection *)theConnection didReceiveData:(NSData *)data
{
    [self dispatchQueueRunType:EDispatchQueueRunTypeAsync checkTerminate:YES asyncBlock:^
    {
        NSInteger dataLength;
        const uint8_t *dataBytes;
        NSInteger bytesWritten;
        NSInteger bytesWrittenSoFar;
        
        dataLength = [data length];
        dataBytes = [data bytes];
        bytesWrittenSoFar = 0;
        do
        {
            bytesWritten = [_fileStream write:&dataBytes[bytesWrittenSoFar] maxLength:dataLength - bytesWrittenSoFar];
            //            assert(bytesWritten != 0);
            if (bytesWritten == 0)
            {
                break;
            }
            else if (bytesWritten == -1)
            {
                [self stopReceiveWithStatus:@"写文件失败！"];
                break;
            }
            else
            {
                bytesWrittenSoFar += bytesWritten;
                _fileWrittenSize += bytesWritten;
                
                [_delegateQueue dispatchThreadAsync:^
                {
                    if ([self.delegate respondsToSelector:@selector(simpleDownloadHelper:fileTotalSize:fileWrittenSize:)] == YES)
                        [self.delegate simpleDownloadHelper:self fileTotalSize:_fileTotalSize fileWrittenSize:_fileWrittenSize];
                }];
            }
        } while (bytesWrittenSoFar != dataLength);
    }];
}

- (void)connection:(NSURLConnection *)theConnection didFailWithError:(NSError *)error
{
    [self dispatchQueueRunType:EDispatchQueueRunTypeAsync checkTerminate:YES asyncBlock:^
    {
        NSString *str = [NSString stringWithFormat:@"网络连接错误：%ud", ToUInt error.code];
        [self stopReceiveWithStatus:str];
    }];
}

- (void)connectionDidFinishLoading:(NSURLConnection *)theConnection
{
    [self dispatchQueueRunType:EDispatchQueueRunTypeAsync checkTerminate:YES asyncBlock:^
    {
        [self closeFileStream];

        if (_netStatus == ENetHelperError)
            return;

        NSFileManager *fm = [NSFileManager defaultManager];
        [fm copyItemAtPath:[self.filePath stringByAppendingPathExtension:@"tmp"] toPath:self.filePath error:nil];

        [fm removeItemAtPath:[self.filePath stringByAppendingPathExtension:@"tmp"] error:nil];

        [self stopReceiveWithStatus:nil];
    }];
}

-(BOOL)connectionShouldUseCredentialStorage:(NSURLConnection *)connection
{
    return YES;
}

- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
    if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust])
    {
        [[challenge sender] useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];
    }
}

#pragma mark -
#pragma mark dealloc

- (void)dealloc
{
    [_delegateQueue release_mm];
    _delegate = nil;
    [_connection release_mm];
    [_filePath release_mm];
    [_urlString release_mm];
    [_fileStream release_mm];
    [_attachment release_mm];
    superdealloc_mm;
}

@end
